//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <mantle.h>
#include <crust.h>
#include <core.h>
#include <_hal.h>

#ifdef _ELADBGAGENT
#include <peimage.h>
#include <elacxt.h>
#include <_eladbg.h>
#include <eladbg.h>
#include <slip.h>
#include <elamstat.h>
#include <stdlib.h>

#define STRING_BUFFER_SIZE      256
#define MESSAGE_BUFFER_SIZE     0x1000

#define IMAGE_TEXT(v) (v)
#define __min(a,b)  (((a) < (b)) ? (a) : (b))

Byte DebugBuf[MESSAGE_BUFFER_SIZE];
DBGBUF MsgBuf;

#define DbgDisablePreemption()
#define DbgEnablePreemption()

typedef enum
{
    ELADBG_MODULE_EXE = 1,
    ELADBG_MODULE_CASE_EXE,
    ELADBG_MODULE_DLL,
    ELADBG_MODULE_CASE_DLL,
    ELADBG_MODULE_UNKNOWNTYPE
} EladbModuleType;

extern Boolean g_bDbgStubEntry;

void UnicToAnsi(char *pszName, const wchar_t *pwszName);
void FlushCache();

//static wchar_t *GetModuleAbsoluteName(wchar_t *pwszModuleName);
static Boolean GetModuleFullNameInPDBPath(const CModule* pModule,
    char *pModFullName);
static EladbModuleType GetModuleType(const wchar_t* pwszModuleName);

static Boolean LoadDebugSymbols();
static Void DbgUpdateSymbols(Address Addr, UInt32 Size, char *pModName);
static Void PrepareException(UInt32 ExceptCode, PELADBGCXTINFO Cxt);
static Boolean IsValidAddress(const UInt32 uAddr);
static Int32 ReadMemory(PByte Addr, Byte *pBuf, UInt32 Size);
static Int32 WriteMemory(PByte Addr, Byte *pBuf, UInt32 Size);
static Boolean SetContext(PELADBGCXTINFO pCxt, InterruptContext *pContext);
static Boolean GetContext(PELADBGCXTINFO pCxt, InterruptContext *pContext);

EXTERN void EladbgInitialize()
{
    if (!BspSetupComPort(COMPORT_DEBUG, NULL)) {
        kprintf(
            "\nFailed to initialize comport for debug or the \
            comport has been set up!\n");
    }

    MsgBuf.Buffer = DebugBuf;
    MsgBuf.MaxLength = MESSAGE_BUFFER_SIZE;
    DbgDisablePreemption();
    CreateServerTransport(L"ELADBG", 1);
    DbgEnablePreemption();
}

EXTERN void EladbgHandler(vector_t vector, InterruptContext *pContext)
{
    PDBG_MESSAGE pDbgm = (PDBG_MESSAGE) MsgBuf.Buffer;
    UInt32 BufSizeAndFlag;
    ELADBGCXTINFO cxtinfo;
    PELADBGCXTINFO pCxt = &cxtinfo;

    /*
     * we already had corrected pc in HandleException().
     */
//    pContext->pc -= sizeof(BREAKPOINT_TYPE);

    CxtinfoFromInterruptContext(pCxt, pContext);

    if (!g_bDbgStubEntry) {
        g_bDbgStubEntry = TRUE;
    }

Resend:
    LoadDebugSymbols();
    PrepareException(vector, pCxt);

    for (;;) {
        BufSizeAndFlag = (UInt32)(-(Int32)MsgBuf.Length);
        if (WriteTransport(MsgBuf.Buffer, BufSizeAndFlag) == (UInt32)-1) {
            kprintf("Failed to Write port\n");
            return;
        }
        BufSizeAndFlag = (UInt32)(-(Int32)MsgBuf.MaxLength);
        MsgBuf.Length = ReadTransport(MsgBuf.Buffer, BufSizeAndFlag);
        if (MsgBuf.Length == (UInt32)-1) {
            kprintf("Failed to read from port\n");
            continue;       // return E_INTERNAL_ERROR;
        }

        // Be pessimistic about the service
        //
        pDbgm->ReturnStatus = STATUS_UNSUCCESSFUL;

//        // Check if the request is from our client
//        //
//        if (pDbgm->ProcessId != (UInt32)IDebugGetProcess(pIDbg)) {
//            DBGOUT(DBGDBG,3,dprintf(
//                   "*** DbgSendWaitContinue: unknown process ID# 0x%08x\n",
//                   pDbgm->ProcessId));
//            continue;
//        }

        // Serving the request
        //
        switch (pDbgm->ApiNumber) {
            case DbgContinueApi:{
                PDBG_CONTINUE a = (PDBG_CONTINUE)(&(pDbgm->u.Continue));
                PELADBGCXTINFO pCxt = (PELADBGCXTINFO)(a + 1);
                kprintf(
                    ">>> DbgContinueApi: continue status %x pc = 0x%08x\n",\
                    a->ContinueStatus, pCxt->PC);

                if (a->ContinueStatus & 0x100) {    // Context follows
                    PELADBGCXTINFO pCxt = (PELADBGCXTINFO)(a + 1);
                    if (!SetContext(pCxt, pContext)) {
                        a->ContinueStatus = -1;
                    }
                }

                if (a->ContinueStatus < 0)
                    return;
                else
                    return;
            }//end DbgContinueApi

            case DbgKillProcessApi:{
//                PDBG_CONTINUE a = &pDbgm->u.Continue;
//                PELADBGCXTINFO pCxt = (PELADBGCXTINFO)(a + 1);
//                return E_SERVER_DIED;
                g_bDbgStubEntry = FALSE;
                GetCurrentProcess()->Kill();
                return;
            }// end DbgKillProcessApi

            case DbgGetVersionApi:{
                PDBG_VERSION a = (PDBG_VERSION)(&(pDbgm->u.Version));

                kprintf(
                    ">>> DbgGetVersionApi: DmKd version %d\n",
                    a->ProtocolVersion);

                // Debug transport protocol version number.
                // This should be incremented if the protocol changes.
                //
                a->ProtocolVersion = 1;

                // The current build number
                //
                a->MinorVersion = 9;     // BuildNumber -- anything goes
                a->MajorVersion = 0xC;   // 0xC - Checked, 0xF - Free
                a->Reserved = 0;         // Reserved for future use

                pDbgm->ReturnStatus = STATUS_SUCCESS;
                break;
            }// end DbgGetVersionApi

            case DbgGetContextApi:{
                PDBG_CONTEXT a = (PDBG_CONTEXT)(&(pDbgm->u.Context));
                PELADBGCXTINFO pCxt;

                kprintf(
                    ">>> DbgGetContextApi: flags %08x\n", a->ContextFlags);

                pCxt = (PELADBGCXTINFO)(MsgBuf.Buffer + MsgBuf.Length);

                if (GetContext(pCxt, pContext)) {
                    pDbgm->ReturnStatus = STATUS_SUCCESS;
                    MsgBuf.Length += sizeof(ELADBGCXTINFO);
                }

                break;
            } //end DbgGetContextApi

            case DbgGetModeApi:{
                PDBG_MODE a = (PDBG_MODE)(&(pDbgm->u.Mode));
                a->DebugerMode = MODE_USER_DEUBG;
                pDbgm->ReturnStatus = STATUS_SUCCESS;
                MsgBuf.Length = sizeof(DBG_MESSAGE);
                break;
            }//end DbgGetModeApi

            case DbgSetContextApi:{
                PDBG_CONTEXT a = (PDBG_CONTEXT)(&(pDbgm->u.Context));
                PELADBGCXTINFO pCxt;

                kprintf(">>> DbgSetContextApi: flags %08x\n",
                    a->ContextFlags);

                pCxt = (PELADBGCXTINFO)(a + 1);

                if (SetContext(pCxt, pContext)) {
                    pDbgm->ReturnStatus = STATUS_SUCCESS;
                }

                MsgBuf.Length -= sizeof(ELADBGCXTINFO);
                break;
            }//end DbgSetContextApi

            case DbgReadVirtualMemoryApi:{
                PDBG_MEMORY a = (PDBG_MEMORY)(&(pDbgm->u.Memory));
                PByte pBuf;
                Int32  rc;
                kprintf(
                    ">>> DbgReadVirtualMemoryApi: addr 0x%08x, size %d\n", \
                    a->TargetAddress, a->TransferCount);

                a->ActualBytes = 0;
                pBuf = MsgBuf.Buffer + MsgBuf.Length;
                rc = (UInt32)(MsgBuf.MaxLength - MsgBuf.Length);
                rc = __min(a->TransferCount, (UInt32)rc);
                rc = ReadMemory((PByte)a->TargetAddress, pBuf, rc);

                if (rc != -1) {
                    pDbgm->ReturnStatus = STATUS_SUCCESS;
                    a->ActualBytes = rc;
                    MsgBuf.Length += rc;
                }
                else {
                    kprintf("DBG can't READ at 0x%08x\n", a->TargetAddress);
                }
                break;
            }// end DbgReadVirtualMemoryApi

            case DbgWriteVirtualMemoryApi:{
                PDBG_MEMORY a = (PDBG_MEMORY)(&(pDbgm->u.Memory));
                Int32 rc;

                kprintf(
                    ">>> DbgWriteVirtualMemoryApi: addr 0x%08x, size %d\n", \
                    a->TargetAddress, a->TransferCount);
                rc = WriteMemory((PByte)a->TargetAddress, (PByte)(a + 1),
                    a->TransferCount);
                if (rc != -1) {
                    pDbgm->ReturnStatus = STATUS_SUCCESS;
                    a->ActualBytes = rc;
                }
                else {
                    kprintf("Eladbg can't WRITE\n");
                    a->ActualBytes = 0;
                }

                MsgBuf.Length -= a->TransferCount;
                break;
            }// end DbgWriteVirtualMemoryApi

            case DbgMarkBreakPointApi:
            case DbgClearBreakPointApi:{
                PDBG_BREAKPOINT a = (PDBG_BREAKPOINT)(&(pDbgm->u.BreakPoint));
                UInt32 n = a->BreakPointCount;
                BREAKPOINT_TYPE OldOpcode;
                BREAKPOINT_TYPE NewOpcode;
                Address BpAddr;
                UInt32 * pBp;
                Int32 rc;

                kprintf(">>> DbgMarkBreakPointApi: entries %d\n", n);
                for (pBp = (PUInt32)(a + 1); n--!= 0; pBp++) {
                    BpAddr = (Address) *pBp++;
                    rc = ReadMemory((PByte)BpAddr, (PByte)&OldOpcode,
                        sizeof(OldOpcode));
                    if (rc != -1) {
                        NewOpcode = (BREAKPOINT_TYPE) *pBp;
                        *pBp = (UInt32) OldOpcode;
                        rc = WriteMemory((PByte)BpAddr, (PByte)&NewOpcode,
                            sizeof(NewOpcode));
                        if (rc != -1) continue;
                    }
                    *pBp = (UInt32)-1;  // s Invalid opcode on all platforms
                }

                pDbgm->ReturnStatus = STATUS_SUCCESS;
                // Error's set individually
                break;
            }// end DbgClearBreakPointApi

            case DbgReadControlSpaceApi:{
                PDBG_MEMORY a = (PDBG_MEMORY)(&(pDbgm->u.Memory));
                PByte pBuf;
                UInt32 len, rc;

                kprintf(
                    ">>> DbgReadControlSpaceApi: addr 0x%08x, size %d\n", \
                    a->TargetAddress, a->TransferCount);

                rc = MsgBuf.MaxLength - MsgBuf.Length;
                len = __min(a->TransferCount, rc);

                // Again, presume we might fail
                //
                a->ActualBytes = 0;

                if (a->TargetAddress < sizeof(DBG_PROCESSOR_STATE)) {
                    rc = sizeof(DBG_PROCESSOR_STATE) - a->TargetAddress;
                    len = __min(len, rc);
                    pBuf = MsgBuf.Buffer + MsgBuf.Length;
                    memset(pBuf, 0, len);
                    rc = len;
                    pDbgm->ReturnStatus = STATUS_SUCCESS;
                    a->ActualBytes = rc;
                    MsgBuf.Length += rc;
                }

                break;
            } // end DbgReadControlSpaceApi

            case DbgRebootApi:{
                BspReboot();
                break;
            } // end DbgRebootApi

            case DbgConnectApi:{
                kprintf(">>> Host debugger send a reconnection pack\n");
//                bNewConnect = TRUE;
                goto Resend;
            } // end DbgConnectApi

            case DbgResetApi:{
                kprintf(">>> Host debugger send a reset pack\n");
                goto Resend;
            } // end DbgResetApi

            default:{
                kprintf(">>> Unknown Dbg API: 0x%08x\n", pDbgm->ApiNumber);
                pDbgm->ReturnStatus = STATUS_UNSUCCESSFUL;

                break;
            } // end default
        } //end switch
    } //end for loop
}

/*
static wchar_t *GetModuleAbsoluteName(wchar_t *pwszModuleName)
{
    assert(pwszModuleName);
    wchar_t *pwszAbsoluteName = pwszModuleName + wcslen(pwszModuleName) - 1;

    while (pwszAbsoluteName > pwszModuleName) {
        if (*pwszAbsoluteName == L'\\' || *pwszAbsoluteName == L'/') {
            break;
        }
        pwszAbsoluteName--;
    }

    if (*pwszAbsoluteName == L'\\' || *pwszAbsoluteName == L'/') {
        pwszAbsoluteName++;
    }
    return pwszAbsoluteName;
}
*/

static Boolean LoadDebugSymbols()
{
    CProcess *pCurrentProcess = GetCurrentProcess();
    if (NULL == pCurrentProcess) return FALSE;
    ModuleView *pModuleView =
        (ModuleView *)(pCurrentProcess->m_moduleList.Next());

    char szModName[_MAX_PATH] = {'\0'};
    while (pModuleView != &(pCurrentProcess->m_moduleList)) {
        if (!pModuleView->m_bLoadedForDebuger) {
            GetModuleFullNameInPDBPath(pModuleView->m_pModule, szModName);
            DbgUpdateSymbols(pModuleView->m_pModule->m_uImageBase,
                            pModuleView->m_pModule->m_uTotalSize,
                            szModName);
            pModuleView->m_bLoadedForDebuger = TRUE;
        }
        pModuleView = (ModuleView *)(pModuleView->Next());
    }
    return TRUE;
}

static Void DbgUpdateSymbols(Address Addr, UInt32 Size, char *pModName)
{
    PDBG_EVENT pDe;
    PDBG_MESSAGE pDbgm;
    Int32 pID,tID;
    Byte* pBuf;
    Byte* pName;

    pDe = (PDBG_EVENT)MsgBuf.Buffer;
    pDbgm  = (PDBG_MESSAGE) MsgBuf.Buffer;

    IProcess *pCurrentProcess = (CProcess *)GetCurrentProcess();
    if (NULL == pCurrentProcess) return;
    pCurrentProcess->GetId(&pID);
    IThread *pCurrentThread = GetCurrentThread();
    if (NULL == pCurrentThread) return;
    pCurrentThread->GetId(&tID);

refill:
    pDe->EventCode = DBG_LOAD_DLL_EVENT;
    pDe->ThreadId  = (UInt32) pID;
    pDe->ProcessId = (UInt32) tID;

    pDe->u.LoadDll.BaseOfDll  = Addr;
    pDe->u.LoadDll.SizeOfDll  = Size;
    pDe->u.LoadDll.NameOffset = sizeof(DBG_EVENT);

    pBuf = (Byte*)MsgBuf.Buffer + sizeof(DBG_EVENT);
    pName = (Byte*)pModName;

    while ((*pBuf++ = *pName++) != IMAGE_TEXT('\0'));

    MsgBuf.Length = pBuf - MsgBuf.Buffer;
    pDe->u.LoadDll.NameLength = MsgBuf.Length - sizeof(DBG_EVENT);

    WriteTransport(MsgBuf.Buffer, MsgBuf.Length);

    //ReadTransport get a continueapi number use to control the sequence
    MsgBuf.Length = ReadTransport(MsgBuf.Buffer, MsgBuf.MaxLength);

    if ((DbgResetApi == pDbgm->ApiNumber) ||
        (DbgConnectApi == pDbgm->ApiNumber)) {
        kprintf("UpdateSymbols::DbgResetApi ReFill!\n");
        goto refill;
    }
}

static Void PrepareException(UInt32 ExceptCode, PELADBGCXTINFO Cxt)
{
    UInt32 Addr;
    Int32 pID,tID;
    PDBG_EVENT pDe = (PDBG_EVENT) MsgBuf.Buffer;

    // Prepare the debug event structure
    //
    if (Cxt != NULL) {
        Addr = Cxt->PC;
    }
    else {
        Addr = 0;
    }

    IProcess *pCurrentProcess = (CProcess *)GetCurrentProcess();
    if (NULL == pCurrentProcess) return;
    pCurrentProcess->GetId(&pID);
    IThread *pCurrentThread = GetCurrentThread();
    if (NULL == pCurrentThread) return;
    pCurrentThread->GetId(&tID);

    pDe->EventCode = DBG_EXCEPTION_EVENT;
    pDe->ProcessId = (UInt32) pID;
    pDe->ThreadId  = (UInt32) tID;

    pDe->u.Exception.FirstChance = 1;
    pDe->u.Exception.ExceptCode  = EXC_BREAKPOINT;
    pDe->u.Exception.ExceptFlags = EXC_FLAG_CONTINUABLE;
    pDe->u.Exception.ExceptInfo  = Addr;

    // PELADBGCXTINFO came in as a parameter.   No need to refetch
    memcpy((PELADBGCXTINFO)(MsgBuf.Buffer + sizeof(DBG_EVENT)), Cxt,
        sizeof(ELADBGCXTINFO));
    MsgBuf.Length = sizeof(DBG_EVENT) + sizeof(ELADBGCXTINFO);
}

static Int32 ReadMemory(PByte Addr, Byte *pBuf, UInt32 Size)
{
    Int32 nCount = Size;

    if (!IsValidAddress((UInt32)Addr)) return -1;

//    if (nCount >= (Int32)(AlignPageUp((UInt32)Addr + 1) - (UInt32)Addr)) {
//        nCount = (Int32)(AlignPageUp((UInt32)Addr + 1) - (UInt32)Addr);
//    }

    memcpy(pBuf, (void *)Addr, nCount);

    return nCount;
}

static Int32 WriteMemory(PByte Addr, Byte *pBuf, UInt32 Size)
{
    Int32 nCount = Size;

    if (!IsValidAddress((UInt32)Addr)) return -1;

//    if (nCount >= (Int32)(AlignPageUp((UInt32)Addr + 1) - (UInt32)Addr)) {
//        nCount = (Int32)(AlignPageUp((UInt32)Addr + 1) - (UInt32)Addr);
//    }

    memcpy((void *)Addr, pBuf, nCount);

    FlushCache();

    return nCount;
}

static Boolean IsValidAddress(const UInt32 uAddr)
{
    CProcess *pCurrentProcess = GetCurrentProcess();
    if (NULL == pCurrentProcess) return FALSE;

    if (pCurrentProcess->m_AS.IsValidAddress(uAddr)) {
        return TRUE;
    }

    return FALSE;
}

static Boolean SetContext(PELADBGCXTINFO pCxt, InterruptContext *pContext)
{
    CxtinfoToInterruptContext(pCxt, pContext);
    return TRUE;
}

static Boolean GetContext(PELADBGCXTINFO pCxt, InterruptContext *pContext)
{
    CxtinfoFromInterruptContext(pCxt, pContext);

    return TRUE;
}

static EladbModuleType GetModuleType(const wchar_t* pwszModuleName)
{
    //strlen(".dll")  = 4 and strlen(".exe")  = 4
    wchar_t *pwszSuffix = (wchar_t *)(pwszModuleName + (wcslen(pwszModuleName) - 4));
    if (!wcscmp(pwszSuffix, L".dll")) {
        return ELADBG_MODULE_DLL;
    }
    else if (!wcscmp(pwszSuffix, L".DLL")) {
        return ELADBG_MODULE_CASE_DLL;
    }
    else if (!wcscmp(pwszSuffix, L".exe")) {
        return ELADBG_MODULE_EXE;
    }
    else if (!wcscmp(pwszSuffix, L".EXE")) {
        return ELADBG_MODULE_CASE_EXE;
    }
    return ELADBG_MODULE_UNKNOWNTYPE;
}

static Boolean GetModuleFullNameInPDBPath(const CModule* pModule,
    char *pModFullName)
{
    if (pModule->m_pImgDebugDirEntry) {
        PIMAGE_DEBUG_DIRECTORY pDebugDir =
            (PIMAGE_DEBUG_DIRECTORY)pModule->m_pImgDebugDirEntry;
        char *pszName =
            (char *)((PByte)(pModule->m_uImageBase)
            + pDebugDir->ulAddressOfRawData + 0x10);
        strcpy(pModFullName, pszName);
    }
    else {
        strcpy(pModFullName,
            "D:\\Elastos20.SDK\\system\\arm_bulverde\\evc.dbg\\bin\\");
        char *psz = pModFullName + strlen(
            "D:\\Elastos20.SDK\\system\\arm_bulverde\\evc.dbg\\bin\\");
        UnicToAnsi(psz, pModule->m_wszName);
        return TRUE;
    }

    EladbModuleType modType = GetModuleType(pModule->m_wszName);
    //The length of "pdb" is 3
    pModFullName[strlen(pModFullName) - 3] = '\0';
    switch (modType) {
        case ELADBG_MODULE_DLL :
            strcat(pModFullName, "dll");
            break;
        case ELADBG_MODULE_CASE_DLL :
            strcat(pModFullName, "DLL");
            break;
        case ELADBG_MODULE_EXE :
            strcat(pModFullName, "exe");
            break;
        case ELADBG_MODULE_CASE_EXE :
            strcat(pModFullName, "EXE");
            break;
        default:
            kprintf("\n%S is unkown module type!\n", pModule->m_wszName);
            return FALSE;
    }
    return TRUE;
}

#endif //_ELADBGAGENT
